/*
 */
package com.ericsson.excelbeanimporter.impl;

import com.ericsson.excelbeanimporter.BeanReader;
import com.ericsson.excelbeanimporter.ExcelBean;
import com.ericsson.excelbeanimporter.ExcelBeanImportException;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;

/**
 * Match the properties of the bean with the index of columns, based on the 
 * configuration passed as a List<String> where each elements in the property
 * of the bean and the index of the list is the index of the column
 * @author eluibon
 */
public class PositionalBeanReader implements BeanReader {

    List<String> configuration;

    private Logger logger = Logger.getLogger(this.getClass().getName());

    public PositionalBeanReader(List<String> configuration) {

        this.configuration = configuration;
    }

    private static Map<Integer,String> cellTypeMapper ;
    
    static {
        cellTypeMapper = new HashMap<Integer,String>() ;
        cellTypeMapper.put(0, "Numeric") ;
        cellTypeMapper.put(1, "String") ;
        cellTypeMapper.put(2, "Formula") ;
        cellTypeMapper.put(3, "Blank") ;
        cellTypeMapper.put(4, "Boolean") ;
        cellTypeMapper.put(5, "Error") ;        
    }
    
    public <T extends ExcelBean> T readFromRow(Row row, Class<T> type) throws ExcelBeanImportException {

        T bean = null;
        try {
            bean = type.newInstance();
        }
        catch (InstantiationException ex) {
            logger.error("Unexpected exception during instantiation of bean : ", ex);
            throw new ExcelBeanImportException(ex) ;
        }
        catch (IllegalAccessException ex) {
            logger.error("Unexpected exception during instantiation of bean : ", ex);
            throw new ExcelBeanImportException(ex) ;
        }

        try {

            for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) {

                Cell cell = row.getCell(i);
                CellReference cellRef = new CellReference(row.getRowNum(), cell.getColumnIndex());
                
                logger.debug("Reading cell " + cellRef.formatAsString());

                bean.setRowNum(row.getRowNum() + 1); // POI use a 0-based numbering for everythin but Excel rows are 1-base

                Class clazz = PropertyUtils.getPropertyType(bean, configuration.get(i)) ;
                if (checkBeanTypeAgainstCellType(cell, clazz)) {                    
                    logger.debug("Cell/Bean Type match : Cell Type = " + cellTypeMapper.get(cell.getCellType()) + " , Bean Type = " + clazz);
                    setBeanValueFromCellValue(cell, bean, clazz, configuration.get(i));
                }
                else {
                    logger.error("Cell/Bean Type Mismatch : Cell Type = " + cellTypeMapper.get(cell.getCellType()) + " , Bean Type = " + clazz);
                    bean.setErrorCode(ExcelBean.TYPE_MISMATCH);
                    bean.setErrorMessage("Type Mismatch : Cell Type = " + cellTypeMapper.get(cell.getCellType()) + " , Bean Type = " + clazz);
                    break ;
                }
            }
        }
        catch (IllegalAccessException ex) {
            logger.error("Error accessing bean property, check configuration.", ex);
            throw new ExcelBeanImportException("Error accessing bean configuration",ex) ;
        }
        catch (InvocationTargetException ex) {
            logger.error("Error accessing bean property, check configuration.", ex);
            throw new ExcelBeanImportException("Error accessing bean configuration",ex) ;
        }
        catch (NoSuchMethodException ex) {
            logger.error("Error accessing bean property, check configuration.", ex);
            throw new ExcelBeanImportException("Error accessing bean configuration",ex) ;
        }

        return bean;
    }

    /**
     * @param cell
     * @param beanType
     * @return 
     */
    private boolean checkBeanTypeAgainstCellType(Cell cell, Class beanPropertyType) {
        
        if (cell.getCellType() == Cell.CELL_TYPE_BLANK) {
            return true ; // il valore 'null' e' sempre ammesso perche' sono non sono ammessi tipo primitivi nel bean (TODO, verificare)
        }

        if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
            return beanPropertyType.equals(String.class);
        }

        if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
            return beanPropertyType.equals(Long.class) || beanPropertyType.equals(Integer.class) || 
                   beanPropertyType.equals(Double.class) || beanPropertyType.equals(Float.class) || 
                   beanPropertyType.equals(Date.class);
        }

        return false;
    }

    /**
     * @param <T>
     * @param cell
     * @param bean
     * @param beanPropertyType 
     */
    private <T extends ExcelBean> void setBeanValueFromCellValue(Cell cell, T bean, Class<?> beanPropertyType, String propertyName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
 
        if (cell.getCellType() == Cell.CELL_TYPE_BLANK) {
            // a blank cell is always returned as a null, since POI retunrs "" for a blank text cell and 0 for a blank numeric cell
            PropertyUtils.setProperty(bean, propertyName, null);
            return ;
        }
        
        if (beanPropertyType.equals(String.class)) {

            logger.debug("Assigning String value '" + cell.getRichStringCellValue() + "' to bean property '" + propertyName + "'");
            PropertyUtils.setProperty(bean, propertyName, cell.getStringCellValue());
        }
        else if ( beanPropertyType.equals(Integer.class) || beanPropertyType.equals(Long.class) || 
                  beanPropertyType.equals(Float.class) || beanPropertyType.equals(Double.class)) {
        
            logger.debug("Assigning Numeric value '" + cell.getNumericCellValue() + "' to bean property '" + propertyName + "'");
            double value = cell.getNumericCellValue() ;
            
            if ( beanPropertyType.equals(Integer.class) ) {
                int i = (int)value ;
                PropertyUtils.setProperty(bean, propertyName, i);
            }
            if ( beanPropertyType.equals(Long.class) ) {
                long l = (long)value ;
                PropertyUtils.setProperty(bean, propertyName, l);
            }            
            if ( beanPropertyType.equals(Float.class) ) {            
                float f = (float)value ;
                PropertyUtils.setProperty(bean, propertyName, f);
            }
            if ( beanPropertyType.equals(Double.class) ) {            
                PropertyUtils.setProperty(bean, propertyName, value);
            }
        }
        else if ( beanPropertyType.equals(Date.class) ) {
        
            logger.debug("Assigning Date value '" + cell.getDateCellValue() + "' to bean property '" + propertyName + "'");
            PropertyUtils.setProperty(bean, propertyName, cell.getDateCellValue());
        }

    }
}
